﻿Imports System
Imports System.Web
Imports System.Collections.Generic

Namespace Dundas.Gauges.WebControl
    ''' <summary>
    ''' HTTP handler for the gauge control
    ''' </summary>
    Public Class GaugeHttpHandler
        Implements IHttpHandler
        ''' <summary>
        ''' Duration of time that the browser should cache our gauge image
        ''' </summary>
        Private Shared ReadOnly imageExpiry As New TimeSpan(0, 1, 0)

        ''' <summary>
        ''' Holds
        ''' </summary>
        Private Shared ReadOnly ImageCache As New GaugeImageCache()

        ''' <summary>
        ''' Add an image to the cache for later retrieval
        ''' </summary>
        ''' <param name="imageData">The image's data</param>
        ''' <returns>A unique ID used to later retrieve the image</returns>
        Public Shared Function AddGaugeImage(ByVal imageData As Byte()) As UInteger
            Dim cache As GaugeImageCache = GaugeHttpHandler.ImageCache
            Return cache.Add(imageData)
        End Function

        ''' <summary>
        ''' Retrieve the image for a gauge
        ''' </summary>
        ''' <param name="context"></param>
        Private Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
            Dim imageID As UInteger

            context.Response.ClearContent()

            ' Get the image ID from the query string
            If UInteger.TryParse(context.Request.QueryString.[Get]("ImageID"), imageID) = False Then
                ' Missing or unparseable ID; return an error
                context.Response.StatusCode = CInt(System.Net.HttpStatusCode.BadRequest)
                Exit Sub
            End If

            ' Try to write the image
            Dim cache As GaugeImageCache = GaugeHttpHandler.ImageCache
            Dim success As Boolean = cache.WriteImage(imageID, context.Response)

            If success = True Then
                ' Success; set the image type and caching
                context.Response.ContentType = "image/png"
                Me.SetCaching(context)
            Else
                ' Non-existant ID; set an error status code
                context.Response.StatusCode = CInt(System.Net.HttpStatusCode.Gone)
            End If
        End Sub

        ''' <summary>
        ''' Setup any caching for our response
        ''' </summary>
        ''' <param name="context"></param>
        Private Sub SetCaching(ByVal context As HttpContext)
            ' Make the browser cache the image
            Dim response As HttpResponse = context.Response
            ' ... but only for the current user
            response.Cache.SetCacheability(HttpCacheability.[Private])
            ' ... and only for a certain time
            response.Cache.SetExpires(DateTime.Now.Add(GaugeHttpHandler.imageExpiry))
        End Sub

        Private ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
            Get
                Return True
            End Get
        End Property

#Region "Image Caching"
        ''' <summary>
        ''' Holds the images of gauges created during a page request so that they can be later retrieved by the HTTP handler
        ''' </summary>
        Private Class GaugeImageCache
            ''' <summary>
            ''' Map of an ID to its image
            ''' </summary>
            Private images As Dictionary(Of UInteger, Byte())

            ''' <summary>
            ''' ID of the last generated image
            ''' </summary>
            Private imageCounter As UInteger

            Public Sub New()
                Me.images = New Dictionary(Of UInteger, Byte())()
            End Sub

            ''' <summary>
            ''' Adds an image to the cache
            ''' </summary>
            ''' <param name="imageData">The image's data</param>
            ''' <returns>A ID uniquely identifying the image</returns>
            Public Function Add(ByVal imageData As Byte()) As UInteger
                ' Use a monitor instead of a reader/writer lock as there is a 50/50 split between reads and writes
                SyncLock Me
                    Me.imageCounter = Me.imageCounter + 1
                    Me.images.Add(Me.imageCounter, imageData)
                    Return Me.imageCounter
                End SyncLock
            End Function

            ''' <summary>
            ''' Write a stored image to an HTTP response
            ''' </summary>
            ''' <param name="id">ID of the image to retrieve</param>
            ''' <param name="response">The response to write the image to</param>
            ''' <returns>True if the image was found and written; false otherwise</returns>
            ''' <remarks>The image is removed upon a successful write</remarks>
            Public Function WriteImage(ByVal id As UInteger, ByVal response As HttpResponse) As Boolean
                SyncLock Me
                    Dim imageData As Byte() = Nothing

                    If Me.images.TryGetValue(id, imageData) = True Then
                        response.BinaryWrite(imageData)
                        Me.images.Remove(id)
                        Return True
                    Else
                        Return False
                    End If
                End SyncLock
            End Function

        End Class
#End Region

    End Class
End Namespace
